package com.sunxboy.platform.logging;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.AppenderAttachableImpl;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.LoggingEvent;

/**
 * Replace logging events before sending them to chained appenders.
 */
public class RegexpReplacingAppender extends AppenderSkeleton implements AppenderAttachable {
    private static final Pattern rulePattern = Pattern.compile("(.)(.+)\\1(.*)\\1");

    private AppenderAttachableImpl attachedAppenders = new AppenderAttachableImpl();

    private Collection<PartialMessageReplacer> eventSubstringReplacer = new ArrayList<PartialMessageReplacer>(10);
    private Collection<WholeMessageReplacer> eventMessageReplacer = new ArrayList<WholeMessageReplacer>(10);

    @Override
    protected void append(LoggingEvent event) {
        if (event.getMessage() instanceof String) {
            String message = (String) event.getMessage();
            // try to replace patterns first
            for (PartialMessageReplacer replacerPartial : eventSubstringReplacer) {
                message = replacerPartial.handleMessage(message);
            }
            // try to find whole expressions
            for (WholeMessageReplacer processor : eventMessageReplacer) {
                if (processor.handleEvent(message, event))
                    return;
            }
            if (!message.equals(event.getMessage()))
                forwardWithNewMessage(message, event);
            else
                passToChainedHandlers(event);
        }
        else {
            passToChainedHandlers(event);
        }
    }

    private void forwardWithNewMessage(String message, LoggingEvent event) {
        Logger eventsLogger = Logger.getLogger(event.getLoggerName());
        if (event.getThrowableInformation() != null)
            passToChainedHandlers(new LoggingEvent(event.getLoggerName(), eventsLogger, event.timeStamp, event.getLevel(), message,
                    event.getThrowableInformation().getThrowable()));
        else
            passToChainedHandlers(new LoggingEvent(event.getLoggerName(), eventsLogger, event.timeStamp, event.getLevel(), message, null));
    }

    protected void passToChainedHandlers(LoggingEvent eventToAppend) {
        attachedAppenders.appendLoopOnAppenders(eventToAppend);
    }

    public void setRegexpToMatch(String stringToMatch) {
        this.eventMessageReplacer.add(new WholeMessageReplacer(stringToMatch));
    }

    public void setRegexpToReplace(String stringToReplace) {
        this.eventSubstringReplacer.add(new PartialMessageReplacer(stringToReplace));
    }

    @Override
    public void close() {
        attachedAppenders.removeAllAppenders();
    }

    @Override
    public boolean requiresLayout() {
        return false;
    }

    public void addAppender(Appender appender) {
        attachedAppenders.addAppender(appender);
    }

    @SuppressWarnings({"rawtypes"})
    public Enumeration getAllAppenders() {
        return attachedAppenders.getAllAppenders();
    }

    public Appender getAppender(String appenderName) {
        return attachedAppenders.getAppender(appenderName);
    }

    public boolean isAttached(Appender appender) {
        return attachedAppenders.isAttached(appender);
    }

    public void removeAllAppenders() {
        attachedAppenders.removeAllAppenders();
    }

    public void removeAppender(Appender appender) {
        attachedAppenders.removeAppender(appender);
    }

    public void removeAppender(String appenderName) {
        attachedAppenders.removeAppender(appenderName);
    }

    /**
     * Replace whole message (and event) if it matches regexp
     */
    private class WholeMessageReplacer {
        private final Pattern pattern;
        private final String replacementString;

        WholeMessageReplacer(String ruleDescriptor) {
            Matcher findings = rulePattern.matcher(ruleDescriptor);
            if (!findings.matches())
                throw new IllegalArgumentException("Invalid replacement rule " + ruleDescriptor);

            pattern = Pattern.compile(findings.group(2), Pattern.DOTALL);
            replacementString = findings.group(3);
        }

        public boolean handleEvent(String message, LoggingEvent event) {
            Matcher matcher = pattern.matcher(message);
            if (matcher.matches()) {
                String replacedString = matcher.replaceFirst(replacementString);
                forwardWithNewMessage(replacedString, event);
                return true;
            }
            return false;
        }
    }

    /**
     * Replace all matching substrings with replacement pattern within message.
     */
    private class PartialMessageReplacer {
        private final Pattern pattern;
        private final String replacementString;

        public PartialMessageReplacer(String regexpToReplace) {
            Matcher findings = rulePattern.matcher(regexpToReplace);
            if (!findings.matches())
                throw new IllegalArgumentException("Invalid replacement rule " + regexpToReplace);

            pattern = Pattern.compile(findings.group(2), Pattern.DOTALL);
            replacementString = findings.group(3);
        }

        public String handleMessage(String message) {
            return pattern.matcher(message).replaceAll(replacementString);
        }
    }
}
